library("matrixStats")

rowMins_R <- function(x, ..., useNames = NA) {
  suppressWarnings({
    res <- apply(x, MARGIN = 1L, FUN = min, ...)
  })
  if (is.na(useNames) || !useNames) names(res) <- NULL
  res
} # rowMins_R()

rowMaxs_R <- function(x, ..., useNames = NA) {
  suppressWarnings({
    res <- apply(x, MARGIN = 1L, FUN = max, ...)
  })
  if (is.na(useNames) || !useNames) names(res) <- NULL
  res
} # rowMaxs_R()

rowRanges_R <- function(x, ..., useNames = NA) {
  suppressWarnings({
    ans <- t(apply(x, MARGIN = 1L, FUN = range, ...))
  })
  
  # Preserve rownames attribute
  dim <- c(dim(x)[1], 2L)
  if (!isTRUE(all.equal(dim(ans), dim))) {
    dim(ans) <- dim
    rownames <- rownames(x)
    if (!is.null(dimnames)) rownames(ans) <- rownames
  }
  if (is.na(useNames) || !useNames) dimnames(ans) <- NULL
  ans
} # rowRanges_R()


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# With and without some NAs
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
for (mode in c("integer", "double")) {
  cat("mode: ", mode, "\n", sep = "")

  for (add_na in c(FALSE, TRUE)) {
    cat("add_na = ", add_na, "\n", sep = "")

    x <- matrix(1:50 + 0.1, nrow = 10L, ncol = 5L)
    if (add_na) {
      x[3:7, c(2, 4)] <- NA_real_
    }
    storage.mode(x) <- mode
    str(x)
    
    # To check names attribute
    dimnames <- list(letters[1:10], LETTERS[1:5])
    
    # Test with and without dimnames on x
    for (setDimnames in c(TRUE, FALSE)) {
      if (setDimnames) dimnames(x) <- dimnames
      else dimnames(x) <- NULL
      # Row/column extremes
      for (na.rm in c(FALSE, TRUE)) {
        # Check names attribute
        for (useNames in c(NA, TRUE, FALSE)) {
          cat("na.rm = ", na.rm, "\n", sep = "")
          
          # Ranges
          cat("range:\n")
          r0 <- rowRanges_R(x, na.rm = na.rm, useNames = useNames)
          r1 <- rowRanges(x, na.rm = na.rm, useNames = useNames)
          r2 <- colRanges(t(x), na.rm = na.rm, useNames = useNames)
          stopifnot(all.equal(r1, r2))
          stopifnot(all.equal(r1, r0))
          
          # Min
          cat("min:\n")
          m0 <- rowMins_R(x, na.rm = na.rm, useNames = useNames)
          m1 <- rowMins(x, na.rm = na.rm, useNames = useNames)
          m2 <- colMins(t(x), na.rm = na.rm, useNames = useNames)
          stopifnot(all.equal(m1, m2))
          stopifnot(all.equal(m1, m0))
          
          # Max
          cat("max:\n")
          m0 <- rowMaxs_R(x, na.rm = na.rm, useNames = useNames)
          m1 <- rowMaxs(x, na.rm = na.rm, useNames = useNames)
          m2 <- colMaxs(t(x), na.rm = na.rm, useNames = useNames)
          stopifnot(all.equal(m1, m2))
          stopifnot(all.equal(m1, m0))
        }
      }
    }
  } # for (add_na ...)
} # for (mode ...)


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# All NAs
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
for (mode in c("integer", "double")) {
  cat("mode: ", mode, "\n", sep = "")
  x <- matrix(NA_real_, nrow = 10L, ncol = 5L)
  storage.mode(x) <- mode
  str(x)
  
  # Test with and without dimnames on x
  for (setDimnames in c(TRUE, FALSE)) {
    if (setDimnames) dimnames(x) <- dimnames
    else dimnames(x) <- NULL
    for (na.rm in c(FALSE, TRUE)) {
      # Check names attribute
      for (useNames in c(NA, TRUE, FALSE)) {
        cat("na.rm = ", na.rm, "\n", sep = "")
        r0 <- rowRanges_R(x, na.rm = na.rm, useNames = useNames)
        r1 <- rowRanges(x, na.rm = na.rm, useNames = useNames)
        r2 <- colRanges(t(x), na.rm = na.rm, useNames = useNames)
        stopifnot(all.equal(r1, r2))
        stopifnot(all.equal(r1, r0))
      }
    }
  }
} # for (mode ...)


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Special cases
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Nx0 matrix
x <- matrix(double(0L), nrow = 5L, ncol = 0L)
r0 <- rowRanges_R(x)
#r1 <- rowRanges(x)
#r_truth <- matrix(c(Inf, -Inf), nrow = nrow(x), ncol = 2L, byrow = TRUE)
#stopifnot(all.equal(r1, r_truth))

# 0xN matrix
x <- t(x)
#r1 <- colRanges(x)
#stopifnot(all.equal(r1, r_truth))

# Nx1 matrix
x <- matrix(1:5, nrow = 5L, ncol = 1L)
# To check names attribute
dimnames <- list(letters[1:5], "A")
r1 <- rowRanges(x)
r_truth <- matrix(1:5, nrow = nrow(x), ncol = 2L, byrow = FALSE)
stopifnot(all.equal(r1, r_truth))
# Check names attribute
dimnames(x) <- dimnames
r0 <- rowRanges_R(x, useNames = TRUE)
r1 <- rowRanges(x, useNames = TRUE)
stopifnot(all.equal(r1, r0))
dimnames(x) <- NULL

# 1xN matrix
x <- t(x)
r1 <- colRanges(x)
stopifnot(all.equal(r1, r_truth))
# Check names attribute
dimnames(x) <- list("a", LETTERS[1:5])
r1 <- colRanges(x, useNames = TRUE)
stopifnot(identical(rownames(r1), colnames(x)))


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Additional tests with NA_integer_, NA_real, NaN, -Inf, +Inf
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
x <- matrix(1:12, nrow = 4L, ncol = 3L)

na_list <- list(
  "integer"       = matrix(1:12, nrow = 4L, ncol = 3L),
  "integer w/ NA" = matrix(NA_integer_, nrow = 4L, ncol = 3L),
  "real"          = matrix(as.double(1:12), nrow = 4L, ncol = 3L),
  "real w/ NA"    = matrix(NA_real_, nrow = 4L, ncol = 3L)
)

na <- na_list[["real"]]
na[2, 2] <- NA
na_list[["real + NA cell"]] <- na

na <- na_list[["real"]]
na[2, ] <- NA
na_list[["real + NA row"]] <- na

na <- na_list[["real"]]
na[2, ] <- NaN
na_list[["real + NaN row"]] <- na

na <- na_list[["real"]]
na[2, 2] <- Inf
na_list[["real + Inf cell"]] <- na

na <- na_list[["real"]]
na[2, ] <- Inf
na_list[["real + Inf row"]] <- na

na <- na_list[["real"]]
na[2, 2] <- NaN
na_list[["real + NaN cell"]] <- na

na <- na_list[["real w/ NA"]]
na[2, 2] <- NaN
na_list[["real w/ NA + NaN cell"]] <- na

na <- na_list[["real w/ NA"]]
na[2, ] <- NaN
na_list[["real w/ NA + NaN row"]] <- na

# To check names attribute
dimnames <- list(letters[1:4], LETTERS[1:3])

# Test with and without dimnames on x
for (setDimnames in c(TRUE, FALSE)) {
  if (setDimnames) dimnames(x) <- dimnames
  else dimnames(x) <- NULL
  for (na.rm in c(FALSE, TRUE)) {
    for (name in names(na_list)) {
      # Check names attribute
      for (useNames in c(NA, TRUE, FALSE)) {
        na <- na_list[[name]]
        cat(sprintf("%s (%s) w/ na.rm = %s:\n", name, typeof(na), na.rm))
        print(na)
        
        cat("  min:\n")
        y0 <- rowMins_R(na, na.rm = na.rm, useNames = useNames)
        str(y0)
        y1 <- rowMins(na, na.rm = na.rm, useNames = useNames)
        str(y1)
        stopifnot(all.equal(y1, y0))
        y1c <- colMins(t(na), na.rm = na.rm, useNames = useNames)
        str(y1c)
        stopifnot(all.equal(y1c, y1))
        
        cat("  max:\n")
        y0 <- rowMaxs_R(na, na.rm = na.rm, useNames = useNames)
        str(y0)
        y1 <- rowMaxs(na, na.rm = na.rm, useNames = useNames)
        str(y1)
        stopifnot(all.equal(y1, y0))
        y1c <- colMaxs(t(na), na.rm = na.rm, useNames = useNames)
        str(y1c)
        stopifnot(all.equal(y1c, y1))
        
        cat("  range:\n")
        y0 <- rowRanges_R(na, na.rm = na.rm, useNames = useNames)
        str(y0)
        y1 <- rowRanges(na, na.rm = na.rm, useNames = useNames)
        str(y1)
        stopifnot(all.equal(y1, y0))
        y1c <- colRanges(t(na), na.rm = na.rm, useNames = useNames)
        str(y1c)
        stopifnot(all.equal(y1c, y1))
      }
    } # for (name ...)
  } # for (na.rm ...)
}
